package com.yyq.reflection;

import com.yyq.reflection.annotation.DefaultNullStringValue;
import org.junit.Test;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 *
 * 反射释义
 *  当一个class文件被jvm加载到内存中,jvm会马上对其进行解析,并将信息封装到一个Class对象中,
 *  如果可以获取到Class对象, 那么就可以通过该Class对象来操作该类
 * @Author yuanyueqiang
 * @Date 2021-11-28 23:46
 */
public class ReflectionDemo {

    /**
     * 1. 获取Class对象的方式:
     *      1.通过完整类名获取     Class.forName(完整类名)
     *      2.通过类名获取        类名.class  如 Car.class
     *      3.通过对象获取        new Car().getClass()
     */
    public void test1() throws ClassNotFoundException {
        Class<?> clazz1 = Class.forName("com.yyq.reflection.Car");//通过全限定类名获取Class
        Class<?> clazz2 = Car.class;
        Class<?> clazz3 = new Car("benzi").getClass();
    }

    /**
     * 反射三大对象及对应的类中的元素
     *      Constructor 构造方法
     *      Method 方法
     *      Field  成员变量
     *
     * 通过Class对象获取构造方法
     *      附加:  需要获取*无访问权限的*的都需要通过 Declared 信息来获取
     *             私有的如果想使用的话, 需要添加暴力反射setAccessible(true)
     */
    @Test
    public void test2() throws Exception {
        Class<?> clazz = Class.forName("com.yyq.reflection.Car");
        Constructor<?>[] constructors1 = clazz.getConstructors();
        Constructor<?>[] constructors2 = clazz.getDeclaredConstructors();
        System.out.println(constructors1.length);
        System.out.println(constructors2.length);
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object o = constructor.newInstance();
        System.out.println(o);
    }
    /**
     * 获取方法
     *  getMethods(): 获取所有有访问权限的方法, 包括继承下来的方法
     *  getDeclaredMethods(): 获取所有方法, 包括继承下来的方法
     * -----方法执行,invoke
     * -----反向获取Class对象 getDeclaredClass()
     * -----获取方法参数
     * -----获取方法返回值类型
     * -----获取方法抛出异常类型
     * -----获取方法注解
     */
    @Test
    public void test3() throws Exception {
        Class<?> clazz = Class.forName("com.yyq.reflection.Car");
        Method[] methods1 = clazz.getMethods();
        Method[] methods2 = clazz.getDeclaredMethods();
        System.out.println(methods1.length);
        System.out.println(methods2.length);
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object o = constructor.newInstance();

        Method drive = clazz.getDeclaredMethod("drive");
        drive.invoke(o);
        Method changeBrand = clazz.getDeclaredMethod("changeBrand", String.class);
        changeBrand.setAccessible(true);
        changeBrand.invoke(o,"goochi");


        Class<?> aClass = drive.getDeclaringClass();
        if(aClass.equals(clazz)){
            System.out.println("aClass和clazz相等");
        }
        if(aClass==clazz){
            System.out.println("aClass和clazz是同一个");
        }
    }
    /**
     * 获取成员变量
     *  getFields()
     *  getDeclaredFields()
     * ------对于存储数据的实体类,获取getSet方法的方式:内省 PropertyDescriptor
     * ------获取成员变量类型
     * ------获取成员变量名称
     * ------获取成员变量注解
     * ------获取成员变量注解类型
     */
    @Test
    public void test4() throws Exception {
        Class<?> clazz = Class.forName("com.yyq.reflection.Car");
        Field[] fields1 = clazz.getFields();
        Field[] fields2 = clazz.getDeclaredFields();
        System.out.println(fields1.length);
        for (Field field:fields2){
            System.out.println(field);
        }
        //先创建一个对象
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object o = constructor.newInstance();
        //通过反射给属性设置值
        Field brandField = clazz.getDeclaredField("brand");
        brandField.setAccessible(true);
        brandField.set(o,"吉利");
        System.out.println(o);
        //内省方式设置getSet方法, 并设置值
        PropertyDescriptor brandDescriptor = new PropertyDescriptor("brand", clazz);
        Method writeBrandMethod = brandDescriptor.getWriteMethod();
        writeBrandMethod.invoke(o,"BWD");
        System.out.println(o);
        Method readBrandMethod = brandDescriptor.getReadMethod();
        System.out.println(readBrandMethod.invoke(o));
    }

    /**
     * 自定义注解与反射操作注解
     */
    @Test
    public void test5() throws Exception {
        Class<?> aClass = Class.forName("com.yyq.reflection.Car");
        Constructor<?> constructor = aClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object o = constructor.newInstance();
        System.out.println(o);
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            int modifiers = field.getModifiers();
            //判断static,final,synchronized,volatile,transient...等修饰符
            boolean aFinal = Modifier.isFinal(modifiers);
            if(aFinal){
                break;
            }
            Annotation[] annotations = field.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                if(annotation instanceof DefaultNullStringValue){
                    field.setAccessible(true);
                    String defaultValue = ((DefaultNullStringValue) annotation).value();
                    if(!"".equals(defaultValue)){
                        field.set(o,"0");
                    }
                    break;
                }
            }
        }
        System.out.println(o);
    }






    public static void main(String[] args) throws Exception {
        //正向:
        Car car = new Car("BWD");
        car.setBrand("benzi");
        car.setColor("red");
        car.setPrice(990000);
        car.drive();
        System.out.println(car.toString());
        //反向:
        //Class<?> clazz = Class.forName("com.yyq.reflection.Car");//通过全限定类名获取Class
        Class<Car> clazz = Car.class;
        Object o = clazz.newInstance();
        PropertyDescriptor brandDescriptor = new PropertyDescriptor("brand",clazz);
        Method writeBrandMethod = brandDescriptor.getWriteMethod();
        writeBrandMethod.invoke(o,"benzi");

        PropertyDescriptor colorDescriptor = new PropertyDescriptor("color",clazz);
        Method writeColorMethod = colorDescriptor.getWriteMethod();
        writeColorMethod.invoke(o,"red");

        PropertyDescriptor priceDescriptor = new PropertyDescriptor("price",clazz);
        Method writePriceMethod = priceDescriptor.getWriteMethod();
        writePriceMethod.invoke(o,980000);

        Method drive = clazz.getDeclaredMethod("drive");
        Method toString = clazz.getDeclaredMethod("toString");
        drive.invoke(o);
        System.out.println(toString.invoke(o));

    }
}
